﻿#include "netconnection.h"
#include <QHostInfo>
#include <QHostAddress>
#include <QMessageBox>
#include <QTcpSocket>
#include <mbase_object.h>
#include <QByteArray>
#include <QString>
#include <QTimer>
#include <QThread>>


/*
 *设置udp通信的端口号为8888
 *设置tcp通信的端口号为9999
 * */
NetConnection::NetConnection(QWidget* parent)
    : QWidget(parent)
    , isAsServer(false)

{
    //整体布局规划
    LineCarType = new QLineEdit("CN111");
    LineCarType->setEnabled(false);
    LabelText = new QLabel("Unconnected");
    //配合使用,每添加一次刷新一次
    carNumberList.append(LineCarType->text() + "(" + getLocalIP() + ")" );

    TobeMainbtn = new QPushButton("成为主控");
    connect(TobeMainbtn, &QPushButton::clicked, this, &NetConnection::slotUdpSend);

    leavebtn = new QPushButton("断开连接");

    connectOverbtn = new QPushButton("连接完成");
    connect(connectOverbtn, &QPushButton::clicked, this, &NetConnection::slotAllConnectionOver);


    hbox1 = new QHBoxLayout();
    hbox1->addWidget(TobeMainbtn);
    hbox1->addWidget(connectOverbtn);
    hbox1->addWidget(leavebtn);
    hbox1->addWidget(LineCarType);
    hbox1->addWidget(LabelText);

    //这里的linedit中的内容只是作为模拟下位机发送过来的信息
    linedit = new QLineEdit();
    sendbtn = new QPushButton("下位机消息");

    //测试使用，主控没有什么消息时需要手写发送的，除了自身的一些东西，主要还是被控
//       connect(sendbtn,&QPushButton::clicked,this,&NetConnection::slotTcpSend);


    hbox2 = new QHBoxLayout();
    hbox2->addWidget(sendbtn);
    hbox2->addWidget(linedit);


    textbrower = new QTextBrowser();
    textbrower_carList = new QTextBrowser();
    hbox3 = new QHBoxLayout();
    hbox3->addWidget(textbrower);
    hbox3->addWidget(textbrower_carList);

    vbox = new QVBoxLayout();
    vbox->addLayout(hbox3);
    vbox->addLayout(hbox2);
    vbox->addLayout(hbox1);
    this->setLayout(vbox);

    //窗口相关图标显示
    QString localIP = getLocalIP();
    this->setWindowTitle("本车IP:" + localIP);
    this->setWindowIcon(QIcon(":/image/SmallWIn.png"));
    this->setWindowFlag(Qt::WindowStaysOnTopHint);//设置始终在最上层

    //只需要创建一个
    udpSocket = new QUdpSocket(this);
    //初始化udp连接端口
    udpConnectionInit();
    connect(udpSocket, &QUdpSocket::readyRead, this, &NetConnection::slotUdpRead);

    //联系前面。在创建之后添加
    countCar();
}

NetConnection::~NetConnection()
{

}

//在windows里面是可以这样实现的
QString NetConnection::getLocalIP()
{
    QString hostName = QHostInfo::localHostName();//本地主机名称
    QHostInfo hostInfo = QHostInfo::fromName(hostName);//主机的信息
    QString localIP = nullptr;
    QList<QHostAddress> addList = hostInfo.addresses();//返回ip地址列表
    if(!addList.isEmpty())
    {
        foreach(QHostAddress ip, addList)
        {
            if(ip.protocol() == QAbstractSocket::IPv4Protocol)
            {
                localIP = ip.toString();
            }
        }
    }
    return localIP;
}


//绑定端口号
void NetConnection::udpConnectionInit()
{
    quint16 peerPort = 8888;
    if(udpSocket->state() != QAbstractSocket::UnconnectedState)
    {
        udpSocket->close();
    }
    else
    {
        udpSocket->bind(peerPort);
    }
}

//在这里实现 成为主控按钮按下的广播信息的发送
void NetConnection::slotUdpSend()
{
    QString str = "EMAINZ";
    QByteArray data = str.toUtf8();
    //这里为广播的地址，255.255.255.255
    QHostAddress peeraAddr = QHostAddress::Broadcast;
    quint16 peerPort = 8888;
    udpSocket->writeDatagram(data, peeraAddr, peerPort);
}

//在这里实现对udp广播信息的接受，并对udp广播信息进行分析
void NetConnection::slotUdpRead()
{
    QHostAddress peerAddr;
    quint16 peerPort;
    while(udpSocket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(datagram.data(), datagram.size(), &peerAddr, &peerPort);
        //这里获取发送信息的IP地址
        quint32  temp = peerAddr.toIPv4Address();
        peerAddr = QHostAddress(temp);
        QString IPStr = peerAddr.toString();
        //将传入的信息转化成字符串，这里不需要进行判别，因为是通过按钮进行固定信息的发送
        QString str = datagram.data();
        handleUdpMsg(str, IPStr);
    }
}

//在这里实现对信息的判别
//1 弹窗 2 确定后实现TCP通信的启动
void NetConnection::handleUdpMsg(QString str, QString IPStr)
{
    if(str == "EMAINZ")
    {
        if(getLocalIP() == IPStr)
        {
            QMessageBox msgBox(this);
            msgBox.setWindowFlag(Qt::WindowStaysOnTopHint);
            msgBox.setWindowIcon(QIcon(":/image/SmallWIn.png"));
            msgBox.setText("Hint");
            msgBox.setInformativeText(tr("To be MainController?"));
            msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
            msgBox.setDefaultButton(QMessageBox::Yes);
            if( msgBox.exec() == QMessageBox::Yes)
            {
                //在这实现tcp服务端的启动
                //启动监听
                tcpServerInit();
                isAsServer = true;
            }
            else
            {
                return;
            }
        }
        else
        {
            QMessageBox msgBox(this);
            msgBox.setWindowFlag(Qt::WindowStaysOnTopHint);
            msgBox.setWindowIcon(QIcon(":/image/SmallWIn.png"));
            msgBox.setText("Hint");
            msgBox.setInformativeText(tr("To be Controller?"));
            msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
            msgBox.setDefaultButton(QMessageBox::Yes);
            if( msgBox.exec() == QMessageBox::Yes)
            {
                //使用udp广播得到的ip进行tcp的连接
                tcpClientInit(IPStr);
                isAsServer = false;
                //确认被控则成为主控按钮不可用
                TobeMainbtn->setEnabled(false);
            }
            else
            {
                return;
            }
        }
    }
}


/**后面的信息发送中，全部用剪切板的信息模拟硬件的行为，
 * 按照直接读取子串的方法，采用case方法实现代码的重构**/

//如果是服务端，则开始创建服务器
void NetConnection::tcpServerInit()
{
    //连接新建开启TCP服务
    tcpServer = new QTcpServer(this);
    //这里需要转换成Qhost地址
    QHostAddress peerAddr = QHostAddress(getLocalIP());
    tcpServer->listen(peerAddr, 9999);
    //监听是否与新客户端的连接
    connect(tcpServer, &QTcpServer::newConnection, this, &NetConnection::slotNewClientConnection);
    textbrower->append("TCP Server is Listening");
}

//服务端检测有客户端连入，为每个客户端创建一个通信套接字
void NetConnection::slotNewClientConnection()
{
    QTcpSocket* tcpSocket = tcpServer->nextPendingConnection();
    //使用tcpsocket可以实现客户端信息传递过来
    QString IP =  tcpSocket->peerAddress().toString();
    textbrower->append(IP + "--Connected!");
    //如果有客户端连接，则可以读取消息了
    connect(tcpSocket, &QTcpSocket::readyRead, this, &NetConnection::slotTcpRead);
    //客户端连上后，发送需要发送请求信息
    ServerTcpSend("RNMZ", NULL, true, NULL, NULL);
}


//客户端只要启动连接
void NetConnection::tcpClientInit(QString IPStr)
{
    QHostAddress TheServerIP = QHostAddress(IPStr);
    TcpClientSocket = new QTcpSocket(this);
    if(TcpClientSocket->state() != TcpClientSocket->ConnectedState)
    {
        //这里要识别传输过来的IP地址
        TcpClientSocket->connectToHost(TheServerIP, 9999);
    }
    //连上后立即执行的操作
    connect(TcpClientSocket, &QTcpSocket::connected, this, &NetConnection::slotClientTcpconnected);
    //调用公共接收消息程序
    connect(TcpClientSocket, &QTcpSocket::readyRead, this, &NetConnection::slotTcpRead);
}

void NetConnection::slotClientTcpconnected()
{
    //这里可以立即执行客户端连接上服务端后的一些操作
}



/***发送消息的程序 *******目前是用软件调试，所有的指令都是人工输入
 * 我的建议是用按钮触发！这样组包和解包程序也好完善，再讨论
 * */
void NetConnection::ServerTcpSend(QString msg, QString msgSocket, bool group,  QString ipStr1,  QString ipStr2)
{
    //服务器群发
    if(group)
    {
        QList<QTcpSocket*> socketList = tcpServer->findChildren<QTcpSocket*>();
        //遍历客户端进行消息的发送
        foreach(QTcpSocket* tmpSocket, socketList)
        {
            //主要消息发送,将服务端相关的指令发送出去
            tmpSocket->write(msg.toUtf8().data());
        }
    }
    //服务器指定发送
    else
    {
        QList<QTcpSocket*> socketList = tcpServer->findChildren<QTcpSocket*>();
        //找到相应的设备进行发送
        if( getLocalIP() == ipStr1 || getLocalIP() == ipStr2 )
        {
            if(getLocalIP() == ipStr1)
            {
                //总控自身处理座指令
                handleTcpMsgServerSelf(msg);
                foreach(QTcpSocket* tmpSocket, socketList)
                {
                    if(tmpSocket->peerAddress().toString() == ipStr2)
                    {
                        tmpSocket->write(msgSocket.toUtf8().data());
                    }
                }
            }
            else if(getLocalIP() == ipStr2)
            {

                //总控自身处理机械臂指令
                handleTcpMsgServerSelf(msgSocket);
                foreach(QTcpSocket* tmpSocket, socketList)
                {
                    if(tmpSocket->peerAddress().toString() == ipStr1)
                    {
                        tmpSocket->write(msg.toUtf8().data());
                    }
                }
            }
        }
        else
        {
            //遍历客户端寻找对应的客户端
            foreach(QTcpSocket* tmpSocket, socketList)
            {
                //机械臂在的车
                if(tmpSocket->peerAddress().toString() == ipStr1)
                    tmpSocket->write(msg.toUtf8().data());
                //座子在的车
                else if(tmpSocket->peerAddress().toString() == ipStr2)
                {
                    tmpSocket->write(msgSocket.toUtf8().data());
                }
            }
        }
    }
}

void NetConnection::ClientTcpSend(QString msg)
{
    //判断套接字是否存在
    if(nullptr == TcpClientSocket)
        return;
    //如果是连接的
    if(TcpClientSocket->state() == TcpClientSocket->ConnectedState)
    {
        TcpClientSocket->write(msg.toUtf8().data());
    }
}



//公共的  读取消息的方式
void NetConnection::slotTcpRead()
{
    if(isAsServer)
    {
        QTcpSocket* tcpSocket = (QTcpSocket*)sender();
        QString ClientIP =  tcpSocket->peerAddress().toString();
        QString msgFromClient = tcpSocket->readAll();
        handleTcpMsgFromClient(msgFromClient, ClientIP);
        textbrower->append(ClientIP + "--" + msgFromClient);
    }
    else
    {
        QString ServerIP =  TcpClientSocket->peerAddress().toString();
        QString msg = TcpClientSocket->readAll();
        handleTcpMsgFromServer(msg, ServerIP);
        textbrower->append(ServerIP + "--" + msg);
    }
}


//服务端处理来自客户端的信息
void NetConnection::handleTcpMsgFromClient(QString msgFromClient, QString ClientIP)
{
    //处理客户端消息
    if(msgFromClient.startsWith("A") && msgFromClient.endsWith("Z"))
    {
        QString msgCarNumber = msgFromClient.mid(1, 5);
        msgCarNumber = msgCarNumber + "(" + ClientIP + ")";
        //这里carnumberList中包含了车牌信息和车辆的具体IP信息，可以结构体实现，这样更简
        carNumberList.append(msgCarNumber);
        //将已经连接上的车辆及其ip信息做一个打印
        countCar();
    }
    else if(msgFromClient.startsWith("B") && msgFromClient.endsWith("Z"))
    {
        QString msgCarState = msgFromClient.mid(1, 6);
        msgCarState = msgCarState + ClientIP;;
        //将收到的车辆状态信息发送出去
        //这里应该根据IP取寻找相应的车辆，而不是车牌，车牌只是作为一种显示, 使用车牌去找也行，凡是后续我不发车牌了，没办法
        emit signalsendCarState(msgCarState);
    }
}



/*****liedit的内容即是下位机发送上来的信息
 * */
//客户端处理来自服务端的信息
void NetConnection::handleTcpMsgFromServer(QString msgFromServer, QString ServerIP)
{
    Q_UNUSED(ServerIP);

    QString msgStart = msgFromServer.at(0);
    QByteArray byteStart = msgStart.toLatin1();
    char* charStart;
    charStart = byteStart.data();
    QString msgSubstring = msgFromServer.mid(1, 2);

    if(msgFromServer.endsWith("Z"))
    {
        switch (*charStart)
        {
            case 'R'://来自服务端request请求
                {
                    if(msgSubstring == "NM")  //车牌请求
                    {
                        QString tempmessagge = "A"  + LineCarType->text() + "Z";
                        ClientTcpSend(tempmessagge);
                    }
                    else if(msgSubstring == "ST")  //状态请求
                    {
                        QString tempmessagge = "B" + linedit->text() + "Z";
                        ClientTcpSend(tempmessagge);
                    }
                };
                break;
            case 'J'://来自服务端的对接指令
                {
                    //被控处理对接指令，转发至下位机执行

                };
                break;
            case 'K'://来自服务端的对接指令
                {
                    //被控处理对接指令，转发至下位机执行
                    //如果是臂的断开，由于执行标志位为0，不会立即执行

                };
                break;
            case 'T':
                {
                    ////收到断开时，插座到位的命令
                };
                break;
            default:
                break;
        }
    }
}


//这里主要是执行指令，请求指令已经写了
void NetConnection::handleTcpMsgServerSelf(QString msgFromServerSelf)
{
    QString msgStart = msgFromServerSelf.at(0);
    QByteArray byteStart = msgStart.toLatin1();
    char* charStart;
    charStart = byteStart.data();
    if(msgFromServerSelf.endsWith("Z"))
    {
        switch (*charStart)
        {
            case 'J'://来自服务端的对接指令
                {
                    //总控处理对接指令，转发至下位机
                    textbrower->append(getLocalIP() + "--" + msgFromServerSelf);

                };
                break;
            case 'K'://来自服务端的对接指令
                {
                    textbrower->append(getLocalIP() + "--" + msgFromServerSelf);
                    //处理对接指令，转发至下位机
                    //如果是臂的断开，由于执行标志位为0，不会立即执行
                };
                break;
            case'T':
                {
                    //收到断开时，插座到位的命令
                    textbrower->append(getLocalIP() + "--" + msgFromServerSelf);

                };
                break;
            default:
                break;
        }
    }

}


//将已经连接的车辆车牌和IP信息显示出来供参考
void NetConnection::countCar()
{
    textbrower_carList->clear();
    for(int i = 0 ; i < carNumberList.size() ; ++i)
    {
        textbrower_carList->append(carNumberList.at(i));
    }
}

//人工确认所有车辆已经连接完毕
void NetConnection::slotAllConnectionOver()
{
    //按照流程框图，此时可以终结udp的定时广播，进入下一步
    connectOverbtn->setEnabled(false);
    //发送请求各分控车状态的信息,
    //@bug1如果图上没有车辆或者没有车牌显示不出来
    //@bug2分控要不能再按
    ServerTcpSend("RSTZ", NULL, true, NULL, NULL);
    //自请求自身的状态
    ServerRequestSelf();
}


//前端将连接好的箭头的车辆信息传入发送执行连接
void NetConnection::LoadtoConnect(QString msgPlug, QString msgSocket, QString ipStr1, QString ipStr2)
{
    //获取一位设备号
    QString plugNumber;
    plugNumber = "J" + msgPlug + "1Z";
    QString socketNumber;
    socketNumber = "J" + msgSocket + "0Z";
    //指定车辆发送
    ServerTcpSend(plugNumber, socketNumber, false, ipStr1, ipStr2);

}


//在这里开始定时请求设备状态、对接状态，这里直接全体请求
void NetConnection::onTimetoRequestEveryCar()
{
    QTimer* timer = new QTimer;
    timer->start(2500);
    //刷新当前状态显示,采用lanmda的方式进行函数的调用
    connect(timer, &QTimer::timeout, this, [ = ]()
    {
        NetConnection::ServerTcpSend("RSTZ", NULL, true, NULL, NULL);
    });
    connect(timer, &QTimer::timeout, this, &NetConnection::ServerRequestSelf);
}

//断开连接信息的发送
void NetConnection::LoadtoDisConnect(QString msgPlug, QString msgSocket, QString ipStr1, QString ipStr2)
{
    QString plugNumber;
    plugNumber = "K" + msgPlug + "0Z";
    QString socketNumber;
    socketNumber = "K" + msgSocket + "1Z";
    //指定车辆发送
    ServerTcpSend(plugNumber, socketNumber, false, ipStr1, ipStr2);
    //请求前面开了一直没关
}



//发送断开连接时，插座已经到位的消息
void NetConnection::SockethasReturntoOrigin(QString plugNumber, QString ipStr_PlugCar)
{
    QString msg_PlugtoDisConnect = "T"  + plugNumber + "1Z";
    ServerTcpSend(msg_PlugtoDisConnect, NULL, false, ipStr_PlugCar, NULL);
}


//总控自身请求自身状态信息
void NetConnection::ServerRequestSelf()
{
    //自请求自身的状态
    QString msgCarState = linedit->text();
    msgCarState = msgCarState.mid(0, 6);
    msgCarState = msgCarState + getLocalIP();;
    //将收到的车辆状态信息发送出去
    emit signalsendCarState(msgCarState);
}


/*
 * @在这里实现对服务器的一个判别，然后将判别的指令向主程序进行一个发送
 * @这里以信号的方式发送，那边将以槽函数的方式接受，如果在里面去调用netconnection的实例函数进行   1同步信息的发送  2同步信息的接受是没法进行响应的
 * @如果是直接在内部实现一个定时器的循环也是不现实的，可能导致频繁的调用导致线程的卡死
 * @最佳方法就是直接对isServer函数写一个公共接口进行调用
 * */
//场景信息的多端同步
void NetConnection::sceneSynicing(QByteArray)
{
//    qDebug() << "the scene state is syncing!";
//    QList<QTcpSocket *> socketList = tcpServer->findChildren<QTcpSocket *>();
//    QString imagebyteString = imageArray.toBase64();
//    imagebyteString = "T" + imagebyteString + "Z";
//        //遍历客户端进行消息的发送
//        foreach(QTcpSocket *tmpSocket , socketList)
//        {
//            //主要消息发送
//            tmpSocket->write(imagebyteString.toUtf8().data());
//        }
}


//测试使用
void NetConnection::slotTcpSend()
{
    ClientTcpSend(linedit->text());
}








